# -*- coding: utf-8 -*-
# @Time    : 2025/4/8 21:15
# @Author  : 自动化助手
# @File    : bilibili_login_optimized.py
# @Software: PyCharm

import json
import time
import logging
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import (TimeoutException,
                                        NoSuchElementException,
                                        StaleElementReferenceException)
from chaojiying_Python.chaojiying import Chaojiying_Client

# 日志配置（带时间戳和调试信息）
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    handlers=[
        logging.FileHandler('bilibili_login.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)


class BilibiliLoginPro:
    # 常量配置
    DEFAULT_TIMEOUT = 10
    QUICK_TIMEOUT = 3
    CAPTCHA_TIMEOUT = 15

    def __init__(self, username, password, soft_id):
        self.service = Service('D:\python-code\chromedriver-win64\chromedriver.exe')
        self.opt = Options()
        self.opt.add_argument("--window-size=1280,720")
        self.opt.add_argument("--disable-blink-features=AutomationControlled")
        self.opt.add_experimental_option("excludeSwitches", ["enable-automation"])
        self.username = username
        self.password = password
        self.soft_id = soft_id
        self.browser = None

    def _wait_element(self, locator, timeout=None):
        """分阶段元素等待策略"""
        timeout = timeout or self.DEFAULT_TIMEOUT
        try:
            # 阶段1：快速检测元素存在
            WebDriverWait(self.browser, min(3, timeout // 2), poll_frequency=0.3).until(
                EC.presence_of_element_located(locator)
            )
            # 阶段2：等待元素可交互
            return WebDriverWait(self.browser, max(1, timeout - 3), poll_frequency=0.5).until(
                EC.element_to_be_clickable(locator)
            )
        except TimeoutException as e:
            logger.error(f" 元素等待超时 | Locator: {locator} | URL: {self.browser.current_url}")
            raise

    def _iframe_context(self, locator):
        """上下文管理器处理iframe切换"""

        class IframeManager:
            def __init__(self, parent, locator):
                self.parent = parent
                self.locator = locator

            def __enter__(self):
                iframe = self.parent._wait_element(self.locator)
                self.parent.browser.switch_to.frame(iframe)
                logger.debug(f" 已切换到iframe: {self.locator}")

            def __exit__(self, exc_type, exc_val, exc_tb):
                self.parent.browser.switch_to.default_content()
                logger.debug(" 已切换回主文档")

        return IframeManager(self, locator)

    def _smart_sleep(self, element=None, base_delay=1):
        """智能休眠策略"""
        if element:
            try:
                if element.is_displayed() and element.is_enabled():
                    time.sleep(base_delay * 0.5)  # 元素就绪时缩短等待
                    return
            except StaleElementReferenceException:
                logger.debug(" 元素状态刷新中...")
        time.sleep(base_delay)

    def _handle_captcha_v3(self):
        """增强版验证码处理（支持重试）"""
        for attempt in range(3):
            try:
                # 等待验证码框架加载
                captcha_frame = self._wait_element(
                    (By.XPATH, '//iframe[contains(@src,"geetest")]'),
                    self.CAPTCHA_TIMEOUT
                )
                self.browser.switch_to.frame(captcha_frame)

                # 获取验证码图片
                pic_element = self._wait_element(
                    (By.XPATH, '//div[@class="geetest_widget"]//canvas'),
                    self.QUICK_TIMEOUT
                )
                self.browser.execute_script("arguments[0].scrollIntoView()", pic_element)
                img = pic_element.screenshot_as_png

                # 调用打码平台
                chaojiying = Chaojiying_Client(self.username, self.password, self.soft_id)
                result = chaojiying.PostPic(img, 9004)
                if not result.get('pic_str'):
                    raise ValueError("验证码识别结果为空")

                # 解析坐标并点击
                for coord in result['pic_str'].split('|'):
                    x, y = map(float, coord.split(','))
                    self.browser.execute_script(f""" 
                        var evt = new MouseEvent('click', {{
                            bubbles: true,
                            clientX: {x},
                            clientY: {y}
                        }});
                        document.elementFromPoint({x},  {y}).dispatchEvent(evt);
                    """)
                    self._smart_sleep(base_delay=0.3)

                # 提交验证
                submit_btn = self._wait_element(
                    (By.XPATH, '//div[contains(@class,"geetest_commit")]'),
                    self.QUICK_TIMEOUT
                )
                submit_btn.click()
                self.browser.switch_to.default_content()

                # 验证是否成功
                if self._verify_captcha_success():
                    return True

            except Exception as e:
                logger.warning(f" 验证码第{attempt + 1}次尝试失败: {str(e)}")
                self.browser.refresh()
                self._smart_sleep(base_delay=2)
        return False

    def _verify_captcha_success(self):
        """验证验证码是否通过"""
        try:
            WebDriverWait(self.browser, 5).until_not(
                EC.presence_of_element_located((By.CLASS_NAME, 'geetest_panel'))
            )
            return True
        except TimeoutException:
            return False

    def login(self):
        """完整的登录流程"""
        try:
            # 初始化浏览器
            self.browser = webdriver.Chrome(service=self.service, options=self.opt)
            self.browser.get('https://www.bilibili.com/')
            logger.info(" 页面加载完成")

            # 触发登录按钮
            login_btn = self._wait_element(
                (By.CSS_SELECTOR, '.header-login-entry'),
                self.QUICK_TIMEOUT
            )
            login_btn.click()
            logger.info(" 已点击登录入口")

            # 在iframe内操作
            with self._iframe_context((By.CSS_SELECTOR, 'iframe.login-iframe')):
                # 输入凭证
                account = self._wait_element((By.XPATH, '//input[@placeholder="请输入账号"]'))
                account.clear()
                account.send_keys(self.username)

                pwd = self._wait_element((By.XPATH, '//input[@placeholder="请输入密码"]'))
                pwd.clear()
                pwd.send_keys(self.password)
                logger.info(" 凭证已输入")

                # 提交登录
                submit = self._wait_element((By.XPATH, '//div[contains(@class,"btn-login")]'))
                submit.click()
                logger.info(" 已触发验证码")

                # 处理验证码
                if not self._handle_captcha_v3():
                    raise RuntimeError("验证码处理失败")

            # 验证登录状态
            self._wait_element((By.CSS_SELECTOR, '.header-avatar'), self.DEFAULT_TIMEOUT)
            logger.info(" 登录成功！")
            return True

        except Exception as e:
            logger.error(f" 登录流程异常: {str(e)}", exc_info=True)
            return False
        finally:
            if self.browser:
                self.browser.quit()


if __name__ == '__main__':
    try:
        # 从配置文件加载凭证
        with open(r'D:\python-code\chaojiying_Python\password.json', 'r', encoding='utf-8') as f:
            config = json.load(f)

            # 执行登录
        bot = BilibiliLoginPro(
            username=config['username'],
            password=config['password'],
            soft_id=config['soft_id']
        )

        if bot.login():
            print("登录流程执行成功，请查看日志文件获取详细信息")
        else:
            print("登录失败，请检查日志")
    except Exception as e:
        logger.critical(f" 程序启动失败: {str(e)}", exc_info=True)